﻿// ------------------------------------------------------------------------------
// <copyright file="PopupManager.cs" company="skumaronline.com">
//    Copyright (C) 2012 Suresh Kumar T.
//
//    This program is free software; you can redistribute it and/or
//    modify it under the terms of the GNU General Public License
//    as published by the Free Software Foundation version 2
//    of the License.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    http://www.gnu.org/licenses/gpl-2.0.html
//
// </copyright>
// ------------------------------------------------------------------------------

namespace StoreAppLib.Controls
{
    using System;
    using Windows.Foundation;
    using Windows.UI;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Media.Animation;

    /// <summary>
    /// Popup manager to display any item with respect to an element
    /// </summary>
    public sealed class PopupManager : ContentControl, IDisposable
    {
        /// <summary>
        /// Pop up
        /// </summary>
        private Popup popup;

        /// <summary>
        /// Pop up content
        /// </summary>
        private FrameworkElement content;

        /// <summary>
        /// Pop up trigger source
        /// </summary>
        private FrameworkElement relativeSource;

        /// <summary>
        /// Relative margin
        /// </summary>
        private Thickness relativeMargin;

        /// <summary>
        /// Pop up alignment with respect to triggering source
        /// </summary>
        private PopupAlignment popupAlignment;

        /// <summary>
        /// Initializes a new instance of the <see cref="PopupManager" /> class
        /// </summary>
        /// <param name="relativeSource">Pop up trigger source</param>
        /// <param name="content">Pop up content</param>
        /// <param name="alignment">Pop up alignment with respect to triggering source</param>
        private PopupManager(FrameworkElement relativeSource, FrameworkElement content, PopupAlignment alignment)
            : this(relativeSource, content, alignment, new Thickness(0))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PopupManager" /> class
        /// </summary>
        /// <param name="relativeSource">Pop up trigger source</param>
        /// <param name="content">Pop up content</param>
        /// <param name="alignment">Pop up alignment with respect to triggering source</param>
        /// <param name="relativeMargin">Pop up margin with respect to triggering source</param>
        private PopupManager(FrameworkElement relativeSource, FrameworkElement content, PopupAlignment alignment, Thickness relativeMargin)
        {
            this.DefaultStyleKey = typeof(PopupManager);

            this.relativeSource = relativeSource;
            this.content = content;
            this.relativeMargin = relativeMargin;

            this.Loaded += this.OnLoaded;

            this.popup = new Popup();
            this.popup.Closed += this.OnPopupClosed;
            this.popup.Opened += this.OnPopupOpened;
            this.popup.IsLightDismissEnabled = true;
            this.popup.Opacity = 1;
            this.popup.Child = this;
            this.Content = content;
            this.popupAlignment = alignment;
        }

        /// <summary>
        /// Open a popup menu with the specified content and return the popup manager instance
        /// </summary>
        /// <param name="relativeSource">Pop up trigger source</param>
        /// <param name="content">Pop up content</param>
        /// <param name="alignment">Pop up alignment with respect to triggering source</param>
        /// <returns>Returns the popup manger</returns>
        public static PopupManager Open(FrameworkElement relativeSource, FrameworkElement content, PopupAlignment alignment)
        {
            return PopupManager.Open(relativeSource, content, alignment, new Thickness(0));
        }

        /// <summary>
        /// Open a popup menu with the specified content and return the popup manager instance
        /// </summary>
        /// <param name="relativeSource">Pop up trigger source</param>
        /// <param name="content">Pop up content</param>
        /// <param name="alignment">Pop up alignment with respect to triggering source</param>
        /// <param name="relativeMargin">Pop up margin with respect to triggering source</param>
        /// <returns>Returns the popup manger</returns>
        public static PopupManager Open(FrameworkElement relativeSource, FrameworkElement content, PopupAlignment alignment, Thickness relativeMargin)
        {
            PopupManager popup = new PopupManager(relativeSource, content, alignment, relativeMargin);
            
            popup.Open();
            return popup;
        }

        /// <summary>
        /// Close popup
        /// </summary>
        public void Close()
        {
            this.popup.IsOpen = false;
        }

        /// <summary>
        /// IDisposable implementation 
        /// </summary>
        public void Dispose()
        {
            if (this.popup != null)
            {
                this.relativeSource = null;
                this.content = null;
                this.popup = null;
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// Opens this content in popup.
        /// </summary>
        private void Open()
        {
            this.popup.IsOpen = false;
            this.Opacity = 0;

            this.popup.IsOpen = true;
        }

        /// <summary>
        /// Fires when the IsOpen property is set to true
        /// </summary>
        /// <param name="sender">Triggering source</param>
        /// <param name="e">Event data for the event</param>
        private void OnPopupOpened(object sender, object e)
        {
            if (this.popup.ActualHeight == 0 || this.popup.ActualWidth == 0)
            {
                this.SizeChanged += this.OnSizeChanged;
            }

            this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

            UIElement root = Window.Current.Content;

            if (root != null)
            {
                GeneralTransform transform = this.relativeSource.TransformToVisual(root);
                Point relativeSourceLocation = transform.TransformPoint(new Point(0, 0));

                Rect bounds = Window.Current.Bounds;

                double horizondalOffset = 0;
                double verticalOffset = 0;

                horizondalOffset = Math.Min(relativeSourceLocation.X + (this.relativeSource.ActualWidth / 2) - (this.content.ActualWidth / 2), bounds.Width - this.content.ActualWidth - 5);
                horizondalOffset = Math.Max(5, horizondalOffset - 2);
                verticalOffset = Math.Min(relativeSourceLocation.Y + (this.relativeSource.ActualHeight / 2) - (this.content.ActualHeight / 2), bounds.Height - this.content.ActualHeight - 5);
                verticalOffset = Math.Max(5, verticalOffset - 1);

                if ((this.popupAlignment & PopupAlignment.Left) == PopupAlignment.Left)
                {
                    horizondalOffset = relativeSourceLocation.X - 2 - this.relativeMargin.Left;
                }

                if ((this.popupAlignment & PopupAlignment.Top) == PopupAlignment.Top)
                {
                    verticalOffset = Math.Max(5, relativeSourceLocation.Y - this.content.ActualHeight - 1 - this.relativeMargin.Top);
                }

                if ((this.popupAlignment & PopupAlignment.Right) == PopupAlignment.Right)
                {
                    horizondalOffset = relativeSourceLocation.X + this.relativeSource.ActualWidth - this.content.ActualWidth - 2 + this.relativeMargin.Right;
                }

                if ((this.popupAlignment & PopupAlignment.Bottom) == PopupAlignment.Bottom)
                {
                    verticalOffset = Math.Min(relativeSourceLocation.Y + this.relativeSource.ActualHeight - 1 + this.relativeMargin.Bottom, bounds.Height - this.content.ActualHeight - 5);
                }

                if (this.popupAlignment == PopupAlignment.TowardsLeft)
                {
                    horizondalOffset = Math.Max(5, relativeSourceLocation.X - this.content.ActualWidth - 2 - this.relativeMargin.Left);
                }

                if (this.popupAlignment == PopupAlignment.TowardsRight)
                {
                    horizondalOffset = Math.Min(relativeSourceLocation.X + this.relativeSource.ActualWidth - 2 + this.relativeMargin.Right, bounds.Width - this.content.ActualWidth - 5);
                }

                this.popup.HorizontalOffset = horizondalOffset;
                this.popup.VerticalOffset = verticalOffset;
            }

            this.Transitions = new TransitionCollection();

            DoubleAnimation transition = new DoubleAnimation();

            transition.From = 0;
            transition.To = 1;
            transition.Duration = TimeSpan.FromSeconds(0.35);

            Storyboard.SetTarget(transition, this);
            Storyboard.SetTargetProperty(transition, "Opacity");

            Storyboard story = new Storyboard();
            story.Children.Add(transition);
            story.Begin();
        }

        /// <summary>
        /// Occurs when either the ActualHeight or the ActualWidth property changes value on a FrameworkElement
        /// </summary>
        /// <param name="sender">The source</param>
        /// <param name="e">Event data for the event</param>
        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (e.NewSize.Width != 0 && e.NewSize.Height != 0)
            {
                this.OnPopupOpened(sender, e);
                this.SizeChanged -= this.OnSizeChanged;
            }
        }

        /// <summary>
        /// Occurs when a FrameworkElement has been constructed and added to the object tree
        /// </summary>
        /// <param name="sender">The source</param>
        /// <param name="e">Event data for the event</param>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Window.Current.Activated += this.OnCurrentWindowActivated;
        }

        /// <summary>
        /// Popup closed
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">Event data for the event</param>
        private void OnPopupClosed(object sender, object e)
        {
            this.Transitions = new TransitionCollection();

            DoubleAnimation transition = new DoubleAnimation();

            transition.From = 1;
            transition.To = 0;
            transition.Duration = TimeSpan.FromSeconds(0.35);

            Storyboard.SetTarget(transition, this);
            Storyboard.SetTargetProperty(transition, "Opacity");

            Storyboard story = new Storyboard();
            story.Children.Add(transition);
            story.Begin();

            this.popup.Child = null;
            Window.Current.Activated -= this.OnCurrentWindowActivated;
        }

        /// <summary>
        /// Occurs when current window successfully activated
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The argument</param>
        private void OnCurrentWindowActivated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)
        {
            if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)
            {
                this.popup.IsOpen = false;
            }
        }
    }
}
